package org.dromara.visitor.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jakarta.annotation.Resource;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.visitor.domain.constants.ApplyStatusEnum;
import org.dromara.visitor.domain.dto.*;
import org.dromara.visitor.domain.entity.*;
import org.dromara.visitor.mapper.ApplyMapper;
import org.dromara.visitor.mapper.BlacklistMapper;
import org.dromara.visitor.mapper.OrganizationMapper;
import org.dromara.visitor.mapper.PhoneBookMapper;
import org.dromara.visitor.service.*;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.DayOfWeek;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjusters;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
public class ApplyServiceImpl extends ServiceImpl<ApplyMapper, Apply> implements ApplyService {
    @Resource
    private ApplyMapper applyMapper;

    @Resource
    private VisConfigService configService;

    @Resource
    private ApplyRecordService applyRecordService;

    @Resource
    private PhoneBookMapper phoneBookMapper;

    @Resource
    private OrganizationMapper organizationMapper;

    @Resource
    private OrgManageService orgManageService;

    @Resource
    private BlacklistMapper blacklistMapper;

    @Resource
    private RuleService ruleService;

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void addApply(ApplyAddReq addReq) {
        LocalDateTime now = LocalDateTime.now();
        Apply apply = new Apply();
        BeanUtils.copyProperties(addReq,apply);
        apply.setStatus(ApplyStatusEnum.APPLY_FIRST.getCode());
        apply.setFirstName(addReq.getVisitName());
        apply.setFirstPhone(apply.getVisitPhone());
        apply.setCreateTime(now);
        apply.setUpdateTime(now);
        Integer expireTime = configService.getExpireTime("expire_time");
        if(expireTime != null){
            //设置了才去做超时的验证，审批端时
            apply.setExpireTime(now.plusMinutes(expireTime));
        }
        //设置二级审批人
        QueryWrapper<PhoneBook> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("approve_phone",apply.getFirstPhone());
        PhoneBook phoneBook = phoneBookMapper.selectOne(queryWrapper);
        if(phoneBook.getApproveLevel() == 2){
            apply.setSecondName(phoneBook.getSecondName());
            apply.setSecondPhone(phoneBook.getSecondPhone());
        }
        //首次申请需进入访客组织
        dealOrgManage(apply);
        applyMapper.insert(apply);
        ApplyRecord record = generateApplyRecord(apply.getId(),apply.getApplyName(),apply.getApplyPhone(),now,ApplyStatusEnum.APPLY_FIRST.getCode(),1);
        applyRecordService.save(record);
    }

    /**
     * 处理访客组织
     * @param apply
     */
    private void dealOrgManage(Apply apply) {
        //判断是否在黑名单
        QueryWrapper<Blacklist> blacklistWrapper = new QueryWrapper<>();
        blacklistWrapper.eq("card_num",apply.getCardNum());
        Blacklist blacklist = blacklistMapper.selectOne(blacklistWrapper);
        if(blacklist != null){
            throw new ServiceException("该访客已加入黑名单");
        }

        //查询初始组织是否存在
        QueryWrapper<Organization> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("is_first",1);
        Organization organization = organizationMapper.selectOne(queryWrapper);
        if(organization == null){
            return;
        }
        //查询是否已经存在
        QueryWrapper<OrgManage> manageQueryWrapper = new QueryWrapper<>();
        manageQueryWrapper.eq("card_num",apply.getCardNum());
        OrgManage manage = orgManageService.getOne(manageQueryWrapper);
        if(manage != null){
            dealRule(manage.getOrgId(),apply.getCardNum(),apply.getVisitTime());
            return;
        }
        manage = new OrgManage();
        manage.setOrgId(organization.getId());
        manage.setApplyName(apply.getApplyName());
        manage.setApplyPhone(apply.getApplyPhone());
        manage.setCardNum(apply.getCardNum());
        manage.setApplyPic(apply.getApplyPic());
        manage.setCreateTime(LocalDateTime.now());
        manage.setUpdateTime(LocalDateTime.now());
        orgManageService.save(manage);
    }

    /**
     * 对访客规则进行过滤
     * @param orgId
     * @param cardNum
     */
    private void dealRule(Long orgId, String cardNum,LocalDateTime visitTime) {
        Rule rule = ruleService.getRuleByOrgId(orgId);
        //以cardNum为key,来查询已经申请的此时判断是否符合规则
        LocalDateTime now = LocalDateTime.now();

        // 获取当前月的第一天 0 点
        LocalDateTime startTime = null;

        // 获取当前月的最后一天 23:59:59
        LocalDateTime endTime =  null;

        switch (rule.getFrequency()){
            case 1:
                //周
                startTime = now.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY))
                    .withHour(0)
                    .withMinute(0)
                    .withSecond(0);
                 endTime = now.with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY))
                     .withHour(23)
                     .withMinute(59)
                     .withSecond(59);
                break;
            case 2:
                //月
                 startTime = now.withDayOfMonth(1).withHour(0).withMinute(0).withSecond(0);

                // 获取当前月的最后一天 23:59:59
                 endTime = now.with(TemporalAdjusters.lastDayOfMonth())
                    .withHour(23)
                    .withMinute(59)
                    .withSecond(59);
                break;
            case 3:
                //季度
                startTime = now.with(now.getMonth().firstMonthOfQuarter())
                    .withDayOfMonth(1)
                    .withHour(0)
                    .withMinute(0)
                    .withSecond(0);
                endTime = now.with(now.getMonth().firstMonthOfQuarter().plus(3))
                    .withDayOfMonth(1)
                    .minusDays(1)
                    .withHour(23)
                    .withMinute(59)
                    .withSecond(59);
                break;
        }
        LambdaQueryWrapper<Apply> queryWrapper = new LambdaQueryWrapper();
        queryWrapper.select(Apply::getId);
        queryWrapper.eq(Apply::getCardNum,cardNum);
        queryWrapper.between(Apply::getVisitTime,startTime,endTime);
        //访问限制
        List<Apply> applyVisits = applyMapper.selectList(queryWrapper);

        //申请限制
        queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.select(Apply::getId);
        queryWrapper.eq(Apply::getCardNum,cardNum);
        LocalDateTime firstTimeOfDay = now.withHour(0).withMinute(0).withSecond(0);
        LocalDateTime lastTimeOfDay = now.withHour(23).withMinute(59).withSecond(59);
        queryWrapper.between(Apply::getCreateTime,firstTimeOfDay,lastTimeOfDay);
        List<Apply> applyToDays =   applyMapper.selectList(queryWrapper);

        //周末限制
        // 检查是否是周末
        if (visitTime.getDayOfWeek() == DayOfWeek.SATURDAY || visitTime.getDayOfWeek() == DayOfWeek.SUNDAY) {
           if(rule.getCanWeekend() == 2){
               throw new ServiceException("周末禁止访问");
           }
        }

        //申请限制
        if(CollectionUtils.isNotEmpty(applyToDays)){
            if(rule.getLimitDayTimes() != null && rule.getLimitDayTimes() > 0){
                if(applyToDays.size() >= rule.getLimitDayTimes()){
                    throw new ServiceException("今日申请次数已用完");
                }
            }
        }

        //访问限制
        Boolean isLimit = false;
        if(CollectionUtils.isNotEmpty(applyVisits)){
            if(rule.getFrequencyDays() != null && rule.getFrequencyDays() > 0){
                if(applyVisits.size() >= rule.getFrequencyDays()){
                    isLimit = true;
                }
            }
        }
       switch (rule.getFrequency()){
            case 1:
               if(isLimit){
                   throw new ServiceException("本周次数已用完");
               }
                break;
           case 2:
               if(isLimit){
                   throw new ServiceException("本月次数已用完");
               }
               break;
           case 3:
               if(isLimit)
                   throw new ServiceException("本季度次数已用完");
               break;
       }

    }

    @Override
    public Page<ApplyResp> queryPageList(ApplyQueryReq req) {
        Page<Apply> page = new Page<>(req.getPageNum(),req.getPageSize());
        LambdaQueryWrapper<Apply> queryWrapper = new LambdaQueryWrapper<>();
        if(StringUtils.isNoneBlank(req.getCardNum())){
            queryWrapper.like(Apply::getCardNum,req.getCardNum());
        }
        if(StringUtils.isNoneBlank(req.getApplyName())){
            queryWrapper.like(Apply::getApplyName,req.getApplyName());
        }
        if(StringUtils.isNoneBlank(req.getApplyPhone())){
            queryWrapper.like(Apply::getApplyPhone,req.getApplyPhone());
        }
        if(req.getStatus() != null){
            queryWrapper.eq(Apply::getStatus,req.getStatus());
        }
        if(req.getVisitStartTime() != null){
            queryWrapper.ge(Apply::getVisitTime,req.getVisitStartTime());
            queryWrapper.le(Apply::getVisitTime,req.getVisitEndTime());
        }
        if(req.getApplyStartTime() != null){
            queryWrapper.ge(Apply::getCreateTime,req.getApplyStartTime());
            queryWrapper.le(Apply::getCreateTime,req.getApplyEndTime());
        }
        queryWrapper.orderByDesc(Apply::getCreateTime);
        this.page(page, queryWrapper);
        Page<ApplyResp> pageResp = new Page<>();
        pageResp.setSize(page.getSize());
        pageResp.setTotal(page.getTotal());
        pageResp.setPages(page.getPages());
        pageResp.setRecords(page.getRecords().stream().map(apply -> {
            ApplyResp applyResp = new ApplyResp();
            BeanUtils.copyProperties(apply,applyResp);
            return applyResp;
        }).collect(Collectors.toList()));
        return pageResp;
    }

    @Override
    public ApplyDetailResp queryDetail(Long id) {
        Apply apply = this.getById(id);
        ApplyDetailResp resp = new ApplyDetailResp();
        BeanUtils.copyProperties(apply,resp);
        List<ApplyRecord> recordList = applyRecordService.listByApplyId(id);
        resp.setRecordList(recordList);
        return resp;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void approvePC(Long id, Integer approveResult) {
        Apply apply = this.getById(id);
        if(apply == null){
            return;
        }
        LocalDateTime now = LocalDateTime.now();
        Integer status = null;
        Integer recordStatus = null;
        if(approveResult == 1){
            //同意
            status = ApplyStatusEnum.PASS.getCode();
            recordStatus = 2;
            //todo 审批通过给闸机下发记录
            noticeAccessGate(apply);
        }else{
            //拒绝
            status = ApplyStatusEnum.REJECT.getCode();
            recordStatus = 3;
        }
        LoginUser loginUser = LoginHelper.getLoginUser();
        apply.setStatus(status);
        apply.setUpdateTime(now);
        //管理员审批暂时归到一级审批
        apply.setFirstApproveTime(now);
        applyMapper.updateById(apply);
        ApplyRecord record = generateApplyRecord(apply.getId(),loginUser.getUsername(),null,now,status,recordStatus);
        applyRecordService.save(record);
    }

    @Override
    public void validatePhoneName(String visitPhone, String visitName) {
        LambdaQueryWrapper<PhoneBook> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(PhoneBook::getApprovePhone, visitPhone);
        queryWrapper.eq(PhoneBook::getApproveName,visitName);
        PhoneBook phoneBook = phoneBookMapper.selectOne(queryWrapper);
        if(phoneBook == null){
            throw new ServiceException("该手机号和姓名不存在");
        }
    }

    @Override
    public Page<ApplyResp> queryAppApplyList(String phoneNumber, Long pageNum, Long pageSize) {
        Page<Apply> page = new Page<>(pageNum,pageSize);
        LambdaQueryWrapper<Apply> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Apply::getFirstPhone,phoneNumber);
        queryWrapper.eq(Apply::getStatus,ApplyStatusEnum.APPLY_FIRST.getCode());
        queryWrapper.or(wrapper -> wrapper.eq(Apply::getSecondPhone,phoneNumber).eq(Apply::getStatus,ApplyStatusEnum.APPLY_SECOND.getCode()));
        queryWrapper.or(wrapper -> wrapper.in(Apply::getStatus,3,4,5)
                                          .and(wrapper1 -> wrapper1.eq(Apply::getSecondPhone,phoneNumber).or().eq(Apply::getFirstPhone,phoneNumber)));
        queryWrapper.orderByDesc(Apply::getCreateTime);
        queryWrapper.orderByAsc(Apply::getStatus);
        this.page(page,queryWrapper);
        Page<ApplyResp> pageResp = new Page<>();
        pageResp.setSize(page.getSize());
        pageResp.setTotal(page.getTotal());
        pageResp.setPages(page.getPages());
        pageResp.setRecords(page.getRecords().stream().map(apply -> {
            ApplyResp applyResp = new ApplyResp();
            BeanUtils.copyProperties(apply,applyResp);
            return applyResp;
        }).collect(Collectors.toList()));
        return pageResp;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void approveApp(Long id, Integer approveResult) {
        Apply apply = this.getById(id);
        if(apply == null){
            return;
        }
        if(apply.getStatus() != ApplyStatusEnum.APPLY_FIRST.getCode()
           && apply.getStatus() != ApplyStatusEnum.APPLY_SECOND.getCode()){
          throw new ServiceException("该单子已审批");
        }
        LocalDateTime now = LocalDateTime.now();
        Integer currentStatus = apply.getStatus();
        Integer nextStatus = null;
        String operator = null;
        String operatorPhone = null;
        Integer recordStatus = 2;
        if(currentStatus == ApplyStatusEnum.APPLY_FIRST.getCode()){
            //判断是否存在二级审批人，存在往下走，不存在直接通过
            if(StringUtils.isNotBlank(apply.getSecondPhone())){
                nextStatus = ApplyStatusEnum.APPLY_SECOND.getCode();
            }else{
                nextStatus = ApplyStatusEnum.PASS.getCode();
            }
            //设置一级审批时间
            apply.setFirstApproveTime(now);
            operator = apply.getFirstName();
            operatorPhone = apply.getFirstPhone();
        }
        if(currentStatus == ApplyStatusEnum.APPLY_SECOND.getCode()){
            nextStatus = ApplyStatusEnum.PASS.getCode();
            operator = apply.getSecondName();
            operatorPhone = apply.getSecondPhone();
            //设置二级审批时间
            apply.setSecondApproveTime(now);
        }
        if(approveResult == 2){
            // 拒绝
            nextStatus = ApplyStatusEnum.REJECT.getCode();
            recordStatus = 3;
        }
        //进行更新
        apply.setStatus(nextStatus);
        apply.setUpdateTime(now);
        this.updateById(apply);
        //todo 审批通过给闸机下发记录
        if(nextStatus == ApplyStatusEnum.PASS.getCode()){
            //todo
            noticeAccessGate(apply);
        }
        ApplyRecord record = generateApplyRecord(apply.getId(),operator,operatorPhone,now,nextStatus,recordStatus);
        applyRecordService.save(record);

    }

    /**
     * 将访客信息下发闸机
     * @param apply
     */
    private void noticeAccessGate(Apply apply) {
        try{

        }catch (Exception e){

        }
    }

    @Override
    public List<ApplyExportExcel> listExport(ApplyQueryReq req) {
        LambdaQueryWrapper<Apply> queryWrapper = new LambdaQueryWrapper<>();
        if(StringUtils.isNoneBlank(req.getCardNum())){
            queryWrapper.like(Apply::getCardNum,req.getCardNum());
        }
        if(StringUtils.isNoneBlank(req.getApplyName())){
            queryWrapper.like(Apply::getApplyName,req.getApplyName());
        }
        if(StringUtils.isNoneBlank(req.getApplyPhone())){
            queryWrapper.like(Apply::getApplyPhone,req.getApplyPhone());
        }
        if(req.getStatus() != null){
            queryWrapper.eq(Apply::getStatus,req.getStatus());
        }
        if(req.getVisitStartTime() != null){
            queryWrapper.ge(Apply::getVisitTime,req.getVisitStartTime());
            queryWrapper.le(Apply::getVisitTime,req.getVisitEndTime());
        }
        if(req.getApplyStartTime() != null){
            queryWrapper.ge(Apply::getCreateTime,req.getApplyStartTime());
            queryWrapper.le(Apply::getCreateTime,req.getApplyEndTime());
        }
        List<Apply> list = this.list(queryWrapper);
        List<ApplyExportExcel> exportList = list.stream().map(apply -> {
            ApplyExportExcel exportExcel = new ApplyExportExcel();
            BeanUtils.copyProperties(apply,exportExcel);
            exportExcel.setVisitTime(apply.getVisitTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
            exportExcel.setCreateTime(apply.getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            switch (apply.getStatus()){
                case 1:
                    exportExcel.setStatus("一级待审批");
                    break;
                case 2:
                    exportExcel.setStatus("二级待审批");
                    break;
                case 3:
                    exportExcel.setStatus("同意");
                    break;
                case 4:
                    exportExcel.setStatus("拒绝");
                    break;
                case 5:
                    exportExcel.setStatus("已过期");
                    break;
                default:
                    exportExcel.setStatus("未知");
                    break;
            }
            return exportExcel;
        }).collect(Collectors.toList());
        return exportList;
    }

    @Override
    public ApplyStatisticsSummaryResp toDaySummary() {
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime startTime = now.withHour(0).withMinute(0).withSecond(0);
        LocalDateTime endTime = now.withHour(23).withMinute(59).withSecond(59);
        LambdaQueryWrapper<Apply> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.between(Apply::getCreateTime,startTime,endTime);
        Long totalApply = applyMapper.selectCount(queryWrapper);
        ApplyStatisticsSummaryResp resp = new ApplyStatisticsSummaryResp();
        resp.setTotalApply(totalApply);
        queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.between(Apply::getFirstApproveTime,startTime,endTime);
        Long firstApprove = applyMapper.selectCount(queryWrapper);
        resp.setFirstApprove(firstApprove);
        queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.between(Apply::getSecondApproveTime,startTime,endTime);
        Long secondApprove = applyMapper.selectCount(queryWrapper);
        resp.setSecondApprove(secondApprove);
        queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Apply::getStatus,ApplyStatusEnum.EXPIRE.getCode());
        queryWrapper.between(Apply::getExpireTime,startTime,endTime);
        Long expireStatus = applyMapper.selectCount(queryWrapper);
        resp.setExpireStatus(expireStatus);
        queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Apply::getStatus,ApplyStatusEnum.PASS.getCode());
        queryWrapper.between(Apply::getUpdateTime,startTime,endTime);
        Long passStatus = applyMapper.selectCount(queryWrapper);
        resp.setPassStatus(passStatus);
        queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Apply::getStatus,ApplyStatusEnum.REJECT.getCode());
        queryWrapper.between(Apply::getUpdateTime,startTime,endTime);
        Long rejectStatus = applyMapper.selectCount(queryWrapper);
        resp.setRejectStatus(rejectStatus);
        return resp;
    }

    @Override
    public ApplyMonthStatusResp monthStatusRate() {
        LambdaQueryWrapper<Apply> queryWrapper = new LambdaQueryWrapper<>();
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime startTime = now.with(TemporalAdjusters.firstDayOfMonth()).withHour(0).withMinute(0).withSecond(0);
        LocalDateTime endTime = now.with(TemporalAdjusters.lastDayOfMonth()).withHour(23).withMinute(59).withSecond(59);
        queryWrapper.between(Apply::getCreateTime,startTime,endTime);
        queryWrapper.select(Apply::getId,Apply::getStatus);
        List<Apply> applies = applyMapper.selectList(queryWrapper);
        ApplyMonthStatusResp resp = new ApplyMonthStatusResp();
        if(CollectionUtils.isNotEmpty(applies)){
            resp.setTotalStatus((long)applies.size());
            Map<Integer,Long> map = applies.stream().collect(Collectors.groupingBy(Apply::getStatus,Collectors.counting()));
            resp.setFirstStatus(map.getOrDefault(ApplyStatusEnum.APPLY_FIRST.getCode(),0L));
            resp.setSecondStatus(map.getOrDefault(ApplyStatusEnum.APPLY_SECOND.getCode(),0L));
            resp.setPassStatus(map.getOrDefault(ApplyStatusEnum.PASS.getCode(),0L));
            resp.setRejectStatus(map.getOrDefault(ApplyStatusEnum.REJECT.getCode(),0L));
            resp.setExpireStatus(map.getOrDefault(ApplyStatusEnum.EXPIRE.getCode(),0L));

            resp.setFirstRate(new BigDecimal(resp.getFirstStatus()/resp.getTotalStatus() * 100).setScale(2, BigDecimal.ROUND_HALF_UP));
            resp.setSecondRate(new BigDecimal(resp.getSecondStatus()/resp.getTotalStatus() * 100).setScale(2, BigDecimal.ROUND_HALF_UP));
            resp.setPassRate(new BigDecimal(resp.getPassStatus()/resp.getTotalStatus() * 100).setScale(2, BigDecimal.ROUND_HALF_UP));
            resp.setRejectRate(new BigDecimal(resp.getRejectStatus()/resp.getTotalStatus() * 100).setScale(2, BigDecimal.ROUND_HALF_UP));
            resp.setExpireRate(new BigDecimal(resp.getExpireStatus()/resp.getTotalStatus() * 100).setScale(2, BigDecimal.ROUND_HALF_UP));
            return resp;
        }
        return resp;
    }

    @Override
    public List<ApplyAndVisitMonthResp> applyAndVisitMonth(LocalDateTime startTime,LocalDateTime endTime) {
        List<ApplyAndVisitMonthResp> list = applyMapper.applyAndVisitMonthList(startTime,endTime);
        return list;
    }

    /**
     * 生成申请记录
     * @param applyName
     * @param applyPhone
     * @param now
     * @param nodeStatus 1-一级待审批，2-二级待审批，3-同意，4-拒绝，5-已过期
     * @param status 1-提交申请，2-同意，3-拒绝
     */
    private ApplyRecord generateApplyRecord(Long applyId,String applyName, String applyPhone, LocalDateTime now, Integer nodeStatus, Integer status) {
        ApplyRecord applyRecord = new ApplyRecord();
        applyRecord.setApplyId(applyId);
        applyRecord.setNodeName(applyName);
        applyRecord.setNodePhone(applyPhone);
        applyRecord.setCreateTime(now);
        applyRecord.setUpdateTime(now);
        applyRecord.setNodeStatus(nodeStatus);
        applyRecord.setStatus(status);
        return applyRecord;
    }
}
